/*
-----------------------------------------------------------------------------
Filename:    Dredd.cpp
Author: Michael Sebright
-----------------------------------------------------------------------------
Provides functions for the main character Dredd
*/

#include "Dredd.h"

/*
 * Dredd Constructer 
 */
Dredd::Dredd(Camera* cam) {
	setupBody(cam->getSceneManager()); // set up Body
	setupAnimations(); // set animations 
	setupCamera(cam); // setup camera
	setupWeapons(cam->getSceneManager()); // create gun

	lives = 3; // initial lives are 3
}

/*
 * Creates the dredd Body mesh based on three meshes
 * There is a mesh for the legs, torso and head
 * Models were created by using blender and texture,
 * and quake to ogre program found on the internet 
 * full info in readme
 */
void Dredd::setupBody(SceneManager* sceneMgr) {
	    // create main model
		mBodyNode = sceneMgr->getRootSceneNode()->createChildSceneNode("DreddBody", Vector3::UNIT_Y * CHAR_HEIGHT);
		mLowerNode = mBodyNode->createChildSceneNode("lower");
		mUpperNode = mBodyNode->createChildSceneNode("upper");
		mHeadNode = mBodyNode->createChildSceneNode("head");

		/* load meshes */
		mLowerEnt = sceneMgr->createEntity("DreddLegs", "dreddlegs.mesh");
		mUpperEnt = sceneMgr->createEntity("DreddTorso", "dreddupper.mesh");
		mHeadEnt = sceneMgr->createEntity("DreddHead","dreddhead.mesh");

		/* scale and move the leggs */
		mLowerNode->rotate(Vector3::UNIT_Y, Radian::Radian(Degree::Degree(270)), Node::TS_PARENT);
		mLowerNode->scale(0.25f, 0.25f, 0.25f);
		mLowerNode->translate(0, 0.5f, 0);	
		mLowerNode->attachObject(mLowerEnt);

		/* scale and move the torso */
		mUpperNode->rotate(Vector3::UNIT_Y, Radian::Radian(Degree::Degree(270)), Node::TS_PARENT);
		mUpperNode->scale(0.25f, 0.25f, 0.25f);
		mUpperNode->translate(0.0f, 3.5f, 0);
		mUpperNode->attachObject(mUpperEnt);

		/* scale and move the head */
		mHeadNode->rotate(Vector3::UNIT_Y, Radian::Radian(Degree::Degree(270)), Node::TS_PARENT);
		mHeadNode->scale(0.25f, 0.25f, 0.25f);
		mHeadNode->translate(0.0f, 7.5f, 0.0f);	
		mHeadNode->attachObject(mHeadEnt);
		
		/* set dredd facing along the -z axi */
		mBodyNode->rotate(Vector3::UNIT_Y, Radian::Radian(Degree::Degree(180)), Node::TS_PARENT);
		
		mKeyDirection = Vector3::ZERO; // no key should be pressed 
}

/*
 * Creates the gun and places it in dredds hand  
 */
void Dredd::setupWeapons(SceneManager* sceneMgr) {
	int i;

	/* intialize bullet objects */
	for(i = 0; i < NUM_ACTIVE_BULLETS; i++) {
		bullets[i] = Bullet(sceneMgr);
	}

	/* Load gun from mesh and place texture on it */
	mGunNode = mBodyNode->createChildSceneNode(Vector3::Vector3(-2,5,1));
	Entity *temp = sceneMgr->createEntity("rifle_body.mesh");
	temp->setMaterialName("dredd/rifle_checker");
	mGunNode->attachObject(temp);

	/* scale and rotate object to fit in dredds hands */
	mGunNode->rotate(Vector3::UNIT_Y,Radian::Radian(Degree::Degree(180)));
	mGunNode->scale(0.5,0.5,0.5);

	ammoIndex = 0;
	attacking = false;
	shotTimer = 0;
}


/*
 * Initializes all the animations and
 * sets the initial idle animation for dredd
 */
void Dredd::setupAnimations() {

		/* list of animation names */
		String animNames[] = {
		"LEGS_IDLE",
		"LEGS_IDLECR",
		"LEGS_RUN",
		"LEGS_TURN",
		"LEGS_WALK",
		"LEGS_WALKCR",
		"LEGS_BACK",
		"TORSO_GESTURE",
		"TORSO_ATTACK",
		"TORSO_ATTACK2",
		"TORSO_DROP",
		"TORSO_RAISE",
		"TORSO_STAND",
		"TORSO_STAND2",
		"BOTH_DEATH1"
		};

		// populate our animation list
		for (int i = 0; i < NUM_ANIMS; i++) {
			if(i < NUM_LOWER_ANIMS) {
				mAnims[i] = mLowerEnt->getAnimationState(animNames[i]);	
			} else {
				mAnims[i] = mUpperEnt->getAnimationState(animNames[i]);	
			}
			mAnims[i]->setLoop(true);
			mFadingIn[i] = false;
			mFadingOut[i] = false;
		}

		/* load death animation */
		mAnims[NUM_ANIMS] = mUpperEnt->getAnimationState(animNames[NUM_ANIMS - 1]);
		mAnims[NUM_ANIMS-1]->setLoop(true);
		mFadingIn[NUM_ANIMS-1] = false;
		mFadingOut[NUM_ANIMS-1] = false;

		// start off in the idle state (top and bottom together)
		setLowerAnimation(LEGS_IDLE);
		setUpperAnimation(TORSO_ATTACK);
}

/*
 * sets leg animation to AnimID
 */
void Dredd::setLowerAnimation(AnimID id, bool reset) {
	if (mLowerAnimID >= 0 && mLowerAnimID < NUM_ANIMS) {
			// if we have an old animation, fade it out
			mFadingIn[mLowerAnimID] = false;
			mFadingOut[mLowerAnimID] = true;
		}

		mLowerAnimID = id;

		if (id != ANIM_NONE) {
			// if we have a new animation, enable it and fade it in
			mAnims[id]->setEnabled(true);
			mAnims[id]->setWeight(0);
			mFadingOut[id] = false;
			mFadingIn[id] = true;
			if (reset) mAnims[id]->setTimePosition(0);
		}
}

/*
 * Set torso animation to AnimID
 */
void Dredd::setUpperAnimation(AnimID id, bool reset) {
	if (mUpperAnimID >= 0 && mUpperAnimID < NUM_ANIMS) {
			// if we have an old animation, fade it out
			mFadingIn[mUpperAnimID] = false;
			mFadingOut[mUpperAnimID] = true;
		}

		mUpperAnimID = id;

		if (id != ANIM_NONE) {
			// if we have a new animation, enable it and fade it in
			mAnims[id]->setEnabled(true);
			mAnims[id]->setWeight(0);
			mFadingOut[id] = false;
			mFadingIn[id] = true;
			if (reset) mAnims[id]->setTimePosition(0);
		}
}

/*
 * Sets up the top down camera that follows dredd 
 */
void Dredd::setupCamera(Camera* cam) {
		// create a pivot at roughly the character's shoulder
		mCameraPivot = cam->getSceneManager()->getRootSceneNode()->createChildSceneNode();
		// this is where the camera should be soon, and it spins around the pivot
		mCameraGoal = mCameraPivot->createChildSceneNode(Vector3(0, 125, 100));
		
		// this is where the camera actually is
		mCameraNode = cam->getSceneManager()->getRootSceneNode()->createChildSceneNode();
		mCameraNode->setPosition(mCameraPivot->getPosition() + mCameraGoal->getPosition());

		mCameraPivot->setFixedYawAxis(true);
		mCameraGoal->setFixedYawAxis(true);
		mCameraNode->setFixedYawAxis(true);

		// our model is quite small, so reduce the clipping planes
		cam->setNearClipDistance(0.01f);
		cam->setFarClipDistance(2000.0f);
		mCameraNode->attachObject(cam);

		mPivotPitch = 0;
}

/*
 * Update camera location based on dredd
 */
void Dredd::updateCamera(Real deltaTime) {
		Vector3 temp = mBodyNode->getPosition();
		temp.x = 0;

		/* dont allow character to go back down */
		if(temp.z > mCameraPivot->getPosition().z) {
			temp.z = mCameraPivot->getPosition().z;
		}

		/* set camera */
		mCameraPivot->setPosition(temp + Vector3::UNIT_Y * CAM_HEIGHT);
		// move the camera smoothly to the goal
		Vector3 goalOffset = mCameraGoal->_getDerivedPosition() - mCameraNode->getPosition();
		mCameraNode->translate(goalOffset * deltaTime * 9.0f);
		// always look at the pivot
		mCameraNode->lookAt(mCameraPivot->_getDerivedPosition(), Node::TS_WORLD);
	}

/*
 * Updates dredds location
 */
void Dredd::updateBody(Real deltaTime) {
		mGoalDirection == Vector3::ZERO; // we will calculate this
		
		if (mKeyDirection != Vector3::ZERO) {
			// calculate actually goal direction in world based on player's key directions
			mGoalDirection += mKeyDirection.z * mCameraNode->getOrientation().zAxis();
			mGoalDirection += mKeyDirection.x * mCameraNode->getOrientation().xAxis();
			
			mGoalDirection.y = 0;

			mGoalDirection.normalise();
		
			/* Move dredd to new location bases on directoin of key input */
			mBodyNode->translate(mKeyDirection.x * deltaTime * RUN_SPEED * mAnims[mLowerAnimID]->getWeight(),
									0, mKeyDirection.z * deltaTime * RUN_SPEED * mAnims[mLowerAnimID]->getWeight(),
									Node::TS_PARENT);
				
			/* Keep dredd with in x boundaries */
				Vector3 temp = mBodyNode->getPosition();
				if(temp.x >= 100) {
					mBodyNode->setPosition(100, 5, temp.z);
				} else if (temp.x <= -100) {
					mBodyNode->setPosition(-100, 5, temp.z);
				}

				/* Define lower z boundary */
				if(temp.z >= mCameraPivot->getPosition().z + 60) {
					mBodyNode->setPosition(mBodyNode->getPosition().x, 5,  mCameraPivot->getPosition().z + 60);
				}
		}
}

/*
 * Update gun and bullets 
 */
void Dredd::updateWeapons(Real realTime) {
	int count = 0;
	shotTimer += realTime; // increase shot timer

	/* iterate through list of bullets */
	while(count < NUM_ACTIVE_BULLETS) {
		if(bullets[ammoIndex].isProjectileActive()) { // updated bullets location if already shot
			bullets[ammoIndex].update(realTime, (Camera *)mCameraNode->getAttachedObject("PlayerCam"));
		} else if((shotTimer >= (1/SHOT_PER_SECOND)) && attacking) { // if within shots per second and mouse is clicked fire 
			bullets[ammoIndex].fire(mGunNode->_getDerivedPosition(), mBodyNode->getOrientation());
			shotTimer = 0; // reset shot timer 
		}
		count++;
		ammoIndex = (++ammoIndex) % NUM_ACTIVE_BULLETS; 
	}
}

/*
 * If key has be lifted up
 */
void Dredd::keyUp(const OIS::KeyEvent& evt) {
	    // keep track of the player's intended direction
		if (evt.key == OIS::KC_W && mKeyDirection.z == -1) mKeyDirection.z = 0;
		else if (evt.key == OIS::KC_A && mKeyDirection.x == -1) mKeyDirection.x = 0;
		else if (evt.key == OIS::KC_S && mKeyDirection.z == 1) mKeyDirection.z = 0;
		else if (evt.key == OIS::KC_D && mKeyDirection.x == 1) mKeyDirection.x = 0;

		if (mKeyDirection.isZeroLength() && mLowerAnimID == LEGS_RUN) {
			// stop running if already moving and the player doesn't want to move
			setLowerAnimation(LEGS_IDLE);
			//if (mTopAnimID == ANIM_RUN_TOP) setTopAnimation(ANIM_IDLE_TOP);
		}
}

/*
 * If key has been pressed
 */
void Dredd::keyDown(const OIS::KeyEvent& evt) {
		
		// keep track of the player's intended direction
		if (evt.key == OIS::KC_W) mKeyDirection.z = -1;
		else if (evt.key == OIS::KC_A) mKeyDirection.x = -1;
		else if (evt.key == OIS::KC_S) mKeyDirection.z = 1;
		else if (evt.key == OIS::KC_D) mKeyDirection.x = 1;

		if (!mKeyDirection.isZeroLength() && mLowerAnimID == LEGS_IDLE) {
			// start running if not already moving and the player wants to move
			setLowerAnimation(LEGS_RUN, true);
			//if (mTopAnimID == ANIM_IDLE_TOP) setTopAnimation(ANIM_RUN_TOP, true);
		}
}

/*
 * If mouse has moved alter dredds orientatoin to pont to mouses location 
 */
void Dredd::mouseMoved(const OIS::MouseEvent& evt) {

	/* update the players aim based on mouse position */
	int mouseX = evt.state.X.abs;
	int mouseY = evt.state.Y.abs;
	int mouseZ = evt.state.Z.abs;
	mGoalDirection.x = mouseX  - evt.state.width/2;
	mGoalDirection.y = 0;
	mGoalDirection.z = -(evt.state.height/2 - mouseY);
	mGoalDirection.normalise();
	
	Quaternion toGoal = mBodyNode->getOrientation().zAxis().getRotationTo(mGoalDirection);

	// calculate how much the character has to turn to face goal direction
	Real yawToGoal = toGoal.getYaw().valueDegrees() ;
			
	mBodyNode->yaw(Degree(yawToGoal));
}

/*
 * If mouse has been clicked
 * if left button clicked allow bulletse to be fired
 */
void Dredd::mouseDown(const OIS::MouseEvent &arg, OIS::MouseButtonID id) {
	if(id == OIS::MB_Left) {
		attacking = true;
	}
}

/*
 * if mouse has been released
 * if left click released, bullets can no longer be fired 
 */
void Dredd::mouseUp(const OIS::MouseEvent &arg, OIS::MouseButtonID id) {
	if(id == OIS::MB_Left) { 
		attacking = false;
	}
}

/*
 * Calls all the updated functions 
 */
void Dredd::addTime(Real deltaTime) {
		updateBody(deltaTime);
		updateAnimations(deltaTime);
		updateCamera(deltaTime);
		updateWeapons(deltaTime);
}

/*
 * fade between animations 
 */
void Dredd::fadeAnimations(Real deltaTime) {
		for (int i = 0; i < NUM_ANIMS; i++) {
			if (mFadingIn[i]) {
				// slowly fade this animation in until it has full weight
				Real newWeight = mAnims[i]->getWeight() + deltaTime * ANIM_FADE_SPEED;
				mAnims[i]->setWeight(Math::Clamp<Real>(newWeight, 0, 1));
				if (newWeight >= 1) mFadingIn[i] = false;
			}
			else if (mFadingOut[i]) {
				// slowly fade this animation out until it has no weight, and then disable it
				Real newWeight = mAnims[i]->getWeight() - deltaTime * ANIM_FADE_SPEED;
				mAnims[i]->setWeight(Math::Clamp<Real>(newWeight, 0, 1));
				if (newWeight <= 0) {
					mAnims[i]->setEnabled(false);
					mFadingOut[i] = false;
				}
			}
		}
}

/*
 * updates animations 
 */
void Dredd::updateAnimations(Real deltaTime) {
		Real baseAnimSpeed = 1;
		Real topAnimSpeed = 1;

		mTimer += deltaTime;

	 

		// increment the current base and top animation times
		if (mLowerAnimID != ANIM_NONE) mAnims[mLowerAnimID]->addTime(deltaTime * baseAnimSpeed);
		//if (mUpperAnimID != ANIM_NONE) mAnims[mUpperAnimID]->addTime(deltaTime * topAnimSpeed);
		if (mUpperAnimID == BOTH_DEATH1){ 
			mAnims[mUpperAnimID]->addTime(deltaTime * topAnimSpeed);
		}

		// apply smooth transitioning between our animations
		fadeAnimations(deltaTime);
}

/*
 * set dredds position and orientation 
 */
void Dredd::setPosition(Vector3 position, Quaternion orientation) {
	mBodyNode->setPosition(position);
	mBodyNode->setOrientation(orientation);
}

/*
 * set dredd lives 
 */
void Dredd::setLives(int lives) {
	Dredd::lives = lives;

	setLowerAnimation(LEGS_IDLE);
	setUpperAnimation(TORSO_ATTACK);

	mCameraPivot->setPosition(Vector3(0, 125, 100));
	setPosition(Vector3(0, 5.5, 0), Quaternion::Quaternion(1,0,-1,0));
}

/*
 * Return a list of active bullets 
 */
std::list<Bullet *> Dredd::activeBullets() {
	int i;
	std::list<Bullet *> active;
	Bullet* temp;
	for(i = 0; i < NUM_ACTIVE_BULLETS; i++) {
		if(bullets[i].isProjectileActive()) {
			temp = &bullets[i];
			active.push_back(temp);
		}
	}

	return active;
}

/*
 * Have dredd loose a life
 * return false if he has 0
 */
bool Dredd::die() {
	lives--;
	if(lives == 0) {
		setUpperAnimation(BOTH_DEATH1);
		setLowerAnimation(BOTH_DEATH1);
		return false;
	}

	mBodyNode->setPosition(0, 5.5, mBodyNode->getPosition().z);
	return true;
}

/*
 * Return dredds location
 */
Vector3 Dredd::getLocation() {
	return mBodyNode->getPosition();
}

/*
 * Return dredd's lives 
 */
int Dredd::getLives() {
	return lives;
	
}